package com.joycessh.service.impl;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.joycessh.common.exception.HwException;
import com.joycessh.entity.Post;
import com.joycessh.mapper.PostMapper;
import com.joycessh.service.PostService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.joycessh.service.UserService;
import com.joycessh.utils.RedisUtil;
import com.joycessh.vo.PostVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author Joycessh
 * @since 2019-08-18
 */
@Slf4j
@Service
public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements PostService {

    @Autowired
    RedisUtil redisUtil;

    @Autowired
    UserService userService;

    @Autowired
    PostMapper postMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Cacheable(cacheNames = "cache_post", key = "'post_' + #id")
    @Override
    public Post get(long id) {
        log.info("--------------->start find post by id: {}", id);
        Post post = this.getById(id);
        Assert.notNull(post, "this post not find");
        return post;
    }

    @Override
    public boolean update(Post post) {
        log.info("--------------->update post");
        return updateById(post);
    }

    @Override
    public boolean delete(long id) {
        log.info("--------------->delete post by id: {}", id);

        Post post = getById(id);
        Assert.notNull(post, "该帖子已被删除");

        // redis缓存了该文章，则清空
        String key = "rank_post_" + id;
        boolean isExist = redisUtil.hasKey(key);
        if (isExist) {
            redisUtil.hdel(key, "post:id", "post:title", "post:commentCount");
        }

        return removeById(id);
    }

    /**
     * 初始化首页的周评论排行榜
     */
    @Override
    public void initIndexWeekRank() {

        // 缓存最近7天的文章评论数量
        List<Post> last7DayPosts = this.list(new QueryWrapper<Post>().ge("create_time", DateUtil.offsetDay(new Date(), -7).toJdkDate())
                .select("id, title, user_id, comment_count, view_count, create_time"));
        for (Post post : last7DayPosts) {
            String key = "day_rank:" + DateUtil.format(post.getCreateTime(), DatePattern.PURE_DATE_PATTERN);

            // 设置有效期
            long between = DateUtil.between(new Date(), post.getCreateTime(), DateUnit.DAY);
            long expireTime = (7 - between) * 24 * 60 * 60;

            // 缓存文章到zset中，评论数量作为排行标准
            redisUtil.zSet(key, post.getId(), post.getCommentCount());
            // 设置有效期
            redisUtil.expire(key, expireTime);

            //缓存文章基本信息（hash结构）
            this.hashCachePostIdAndTitle(post);
        }
        this.zUnionAndStoreLast7DaysForLastWeekRank();
    }

    /**
     * hash结构缓存文章标题和id
     * @param post
     */
    private void hashCachePostIdAndTitle(Post post) {
        boolean isExist = redisUtil.hasKey("rank_post_" + post.getId());
        if (!isExist) {
            long between = DateUtil.between(new Date(), post.getCreateTime(), DateUnit.DAY);
            long expireTime = (7 - between) * 24 * 60 * 60;

            //缓存文章基本信息（hash结构）
            redisUtil.hset("rank_post_" + post.getId(), "post:id", post.getId(), expireTime);
            redisUtil.hset("rank_post_" + post.getId(), "post:title", post.getTitle(), expireTime);
            redisUtil.hset("rank_post_" + post.getId(), "post:commentCount", post.getCommentCount(), expireTime);
        }
    }

    @Override
    public void zUnionAndStoreLast7DaysForLastWeekRank() {
        String prefix = "day_rank:";
        List<String> keys = new ArrayList<>();

        String key = prefix + DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN);
        for (int i = -7; i < 0; i++) {
            Date date = DateUtil.offsetDay(new Date(), i).toJdkDate();
            keys.add(prefix + DateUtil.format(date, DatePattern.PURE_DATE_PATTERN));
        }
        redisUtil.zUnionAndStore(key, keys, "last_week_rank");
    }

    /**
     * 给set里的文章评论加1，并且重新union7天的评论数量
     * @param postId
     * @param isIncr
     */
    @Override
    public void incrZsetValueAndUnionForLastWeekRank(Long postId, boolean isIncr) {
        String dayRank = "day_rank:" + DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN);
        // 文章阅读加一
        redisUtil.zIncrementScore(dayRank, postId, isIncr ? 1 : -1);

        this.hashCachePostIdAndTitle(this.getById(postId));
        // 重新union最近7天
        this.zUnionAndStoreLast7DaysForLastWeekRank();
    }

    @Override
    @Cacheable(cacheNames = "cache_post", key = "'page_' + #page.current + '_' + #page.size " +
            "+ '_query_' +#userId  + '_' + #categoryId + '_' + #level  + '_' + #recommend  + '_' + #order")
    public IPage paging(Page page, Long userId, Long categoryId, Integer level, Boolean recommend, String order) {
        if (level == null) {
            level = -1;
        }

        QueryWrapper queryWrapper = new QueryWrapper<Post>()
                .eq(userId != null, "user_id", userId)
                .eq(categoryId != null && categoryId != 0, "category_id", categoryId)
                .gt(level > 0, "level", 0)
                .eq(level == 0, "level", 0)
                .eq(recommend != null, "recommend", recommend)
                .orderByDesc(order);

        IPage<PostVo> pageData = postMapper.selectPosts(page, queryWrapper);
        return pageData;
    }

    @Override
    public PostVo selectOne(QueryWrapper<Post> wrapper) {

        PostVo postVo = postMapper.selectOne(wrapper);
        if (postVo == null) {
            throw new HwException("该帖子已被删除");
        }
        return postVo;
    }
}
